/*
 *	Copyright (C) 2003-2006 Gabest
 *	http://www.gabest.org
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "stdafx.h"
#include "Stream.h"
#include <stdio.h>

namespace ssf
{
Stream::Stream()
    : m_encoding(none)
    , m_line(0)
    , m_col(-1)
{

}

Stream::~Stream()
{
}

void Stream::ThrowError(LPCTSTR fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    int len = _vsctprintf(fmt, args) + 1;
    CString str;
    if(len > 0) _vstprintf_s(str.GetBufferSetLength(len), len, fmt, args);
    va_end(args);

    throw Exception(_T("Error (Ln %d Col %d): %s"), m_line + 1, m_col + 1, str);
}

bool Stream::IsWhiteSpace(int c, LPCWSTR morechars)
{
    return c != 0xa0 && iswspace(c) || morechars && wcschr(morechars, (WCHAR)c);
}

//

InputStream::InputStream()
{

}

InputStream::~InputStream()
{
}

int InputStream::NextChar()
{
    if(m_encoding == none)
    {
        m_encoding = unknown;

        switch(NextByte())
        {
        case 0xef:
            if(NextByte() == 0xbb && NextByte() == 0xbf) m_encoding = utf8;
            break;
        case 0xff:
            if(NextByte() == 0xfe) m_encoding = utf16le;
            break;
        case 0xfe:
            if(NextByte() == 0xff) m_encoding = utf16be;
            break;
        }
    }

    if(m_encoding == unknown)
    {
        throw Exception(_T("unknown character encoding, missing BOM"));
    }

    int i, c;

    int cur = NextByte();

    switch(m_encoding)
    {
    case utf8:
        for(i = 7; i >= 0 && (cur & (1 << i)); i--);
        cur &= (1 << i) - 1;
        while(++i < 7)
        {
            c = NextByte();
            if(c == EOS)
            {
                cur = EOS;
                break;
            }
            cur = (cur << 6) | (c & 0x3f);
        }
        break;
    case utf16le:
        c = NextByte();
        if(c == EOS)
        {
            cur = EOS;
            break;
        }
        cur = (c << 8) | cur;
        break;
    case utf16be:
        c = NextByte();
        if(c == EOS)
        {
            cur = EOS;
            break;
        }
        cur = cur | (c << 8);
        break;
    case wchar:
        break;
    }

    return cur;
}

int InputStream::PushChar()
{
    int c = NextChar();
    m_queue.AddTail(c);
    return c;
}

int InputStream::PopChar()
{
    if(m_queue.IsEmpty()) ThrowError(_T("fatal stream error"));

    int c = m_queue.RemoveHead();

    if(c != EOS)
    {
        if(c == '\n')
        {
            m_line++;
            m_col = -1;
        }
        m_col++;
    }

    return c;
}

int InputStream::PeekChar()
{
    while(m_queue.GetCount() < 2) PushChar();

    ASSERT(m_queue.GetCount() == 2);

    if(m_queue.GetHead() == '/' && m_queue.GetTail() == '/')
    {
        while(!m_queue.IsEmpty()) PopChar();
        int c;
        do
        {
            PushChar();
            c = PopChar();
        }
        while(!(c == '\n' || c == EOS));
        return PeekChar();
    }
    else if(m_queue.GetHead() == '/' && m_queue.GetTail() == '*')
    {
        while(!m_queue.IsEmpty()) PopChar();
        int c1, c2;
        PushChar();
        do
        {
            c2 = PushChar();
            c1 = PopChar();
        }
        while(!((c1 == '*' && c2 == '/') || c1 == EOS));
        PopChar();
        return PeekChar();
    }

    return m_queue.GetHead();
}

int InputStream::GetChar()
{
    if(m_queue.GetCount() < 2) PeekChar();
    return PopChar();
}

int InputStream::SkipWhiteSpace(LPCWSTR morechars)
{
    int c = PeekChar();
    for(; IsWhiteSpace(c, morechars); c = PeekChar())
        GetChar();
    return c;
}

// FileInputStream

FileInputStream::FileInputStream(const TCHAR* fn)
    : m_file(NULL)
{
    if(_tfopen_s(&m_file, fn, _T("r")) != 0) ThrowError(_T("cannot open file '%s'"), fn);
}

FileInputStream::~FileInputStream()
{
    if(m_file)
    {
        fclose(m_file);
        m_file = NULL;
    }
}

int FileInputStream::NextByte()
{
    if(!m_file) ThrowError(_T("file pointer is NULL"));
    return fgetc(m_file);
}

// MemoryInputStream

MemoryInputStream::MemoryInputStream(BYTE* pBytes, int len, bool fCopy, bool fFree)
    : m_pBytes(NULL)
    , m_pos(0)
    , m_len(len)
{
    if(fCopy)
    {
        m_pBytes = DNew BYTE[len];
        if(m_pBytes) memcpy(m_pBytes, pBytes, len);
        m_fFree = true;
    }
    else
    {
        m_pBytes = pBytes;
        m_fFree = fFree;
    }

    if(!m_pBytes) ThrowError(_T("memory stream pointer is NULL"));
}

MemoryInputStream::~MemoryInputStream()
{
    if(m_fFree) delete [] m_pBytes;
    m_pBytes = NULL;
}

int MemoryInputStream::NextByte()
{
    if(!m_pBytes) ThrowError(_T("memory stream pointer is NULL"));
    if(m_pos >= m_len) return Stream::EOS;
    return (int)m_pBytes[m_pos++];
}

// WCharInputStream

WCharInputStream::WCharInputStream(CStringW str)
    : m_str(str)
    , m_pos(0)
{
    m_encoding = Stream::wchar; // HACK: it should return real bytes from NextByte (two per wchar_t), but this way it's a lot more simple...
}

int WCharInputStream::NextByte()
{
    if(m_pos >= m_str.GetLength()) return Stream::EOS;
    return m_str[m_pos++];
}

// OutputStream

OutputStream::OutputStream(encoding_t e)
{
    m_encoding = e;
    m_bof = true;
}

OutputStream::~OutputStream()
{
}

void OutputStream::PutChar(WCHAR c)
{
    if(m_bof)
    {
        m_bof = false;

        switch(m_encoding)
        {
        case utf8:
        case utf16le:
        case utf16be:
            PutChar(0xfeff);
            break;
        }
    }

    switch(m_encoding)
    {
    case utf8:
        if(0 <= c && c < 0x80) // 0xxxxxxx
        {
            NextByte(c);
        }
        else if(0x80 <= c && c < 0x800) // 110xxxxx 10xxxxxx
        {
            NextByte(0xc0 | ((c << 2) & 0x1f));
            NextByte(0x80 | ((c << 0) & 0x3f));
        }
        else if(0x800 <= c && c < 0xFFFF) // 1110xxxx 10xxxxxx 10xxxxxx
        {
            NextByte(0xe0 | ((c << 4) & 0x0f));
            NextByte(0x80 | ((c << 2) & 0x3f));
            NextByte(0x80 | ((c << 0) & 0x3f));
        }
        else
        {
            NextByte('?');
        }
        break;
    case utf16le:
        NextByte(c & 0xff);
        NextByte((c >> 8) & 0xff);
        break;
    case utf16be:
        NextByte((c >> 8) & 0xff);
        NextByte(c & 0xff);
        break;
    case wchar:
        NextByte(c);
        break;
    }
}

void OutputStream::PutString(LPCWSTR fmt, ...)
{
    CStringW str;

    va_list args;
    va_start(args, fmt);
    int len = _vscwprintf(fmt, args) + 1;
    if(len > 0) vswprintf_s(str.GetBufferSetLength(len), len, fmt, args);
    va_end(args);

    LPCWSTR s = str;
    while(*s) PutChar(*s++);
}

// WCharOutputStream

WCharOutputStream::WCharOutputStream()
    : OutputStream(wchar)
{
}

void WCharOutputStream::NextByte(int b)
{
    m_str += (WCHAR)b;
}

// DebugOutputStream

DebugOutputStream::DebugOutputStream()
    : OutputStream(wchar)
{
}

DebugOutputStream::~DebugOutputStream()
{
    TRACE(_T("%s\n"), m_str);
}

void DebugOutputStream::NextByte(int b)
{
    if(b == '\n')
    {
        TRACE(_T("%s\n"), m_str);
        m_str.Empty();
    }
    else if(b != '\r') m_str += (WCHAR)b;
}
}